package deepr.lib;

import deepr.anno.LifeCycle;
import deepr.anno.Type;
import deepr.core.*;
import deepr.entity.DeeprRequest;
import deepr.exception.DeeprAssert;
import deepr.exception.DeeprException;
import deepr.exception.LifeException;
import deepr.filter.FilterParser;
import deepr.src.graph.GraphDisposeContext;
import lombok.extern.slf4j.Slf4j;

import java.util.HashMap;
import java.util.List;
import java.util.Map;


@Slf4j
public class GraphQlLib {

  public static void main2(String[] args) throws NoSuchMethodException {
//    SnowIdHandler.class.getMethod("select",
//        HandlerContext.class, String.class, String.class, Object.class);
  }

  //    public static String
  //%{
  //  "device" => %{
  //    "()" => %{},
  //    "=>" => ["id", "name", "model", "status", "imgs"],
  //    "[]" => 1,
  //    "match" => %{},
  //    "optional" => %{"director" => ["id", "name"], "room" => ["id", "name"]},
  //    "total_page" => true
  //  }
  //}
  public static String list(GraphDisposeContext ctx, DeeprRequest deeprRequest) throws Exception {
    deeprRequest.valid();
    String object = deeprRequest.getObject();
    int pageSize = 20;
    deeprRequest.setPageSize(pageSize);
    ctx.setTargetObject(object);
    List<Map<String, Object>> filter = deeprRequest.getFilter();
    StringBuilder filterStr = new StringBuilder();
    if (filter.size() > 0) {
      FilterParser filterParser = new FilterParser(ctx);
      filterStr = filterParser.parse(object, filter);
      ctx.getDbFilterBehavior().resetDepthObjectFilter();
    }
    String[] select = deeprRequest.getSelect();
    Map<String, String[]> optional = deeprRequest.getOptional();
    Map<String, String[]> match = deeprRequest.getMatch();
    Integer page = deeprRequest.getPage();
    SchemaData schemaData = SchemaManager.getSchemaData(object);
    ctx.setSchemaData(schemaData);
    StringBuilder matchStr = new StringBuilder();
    StringBuilder withObjStr = new StringBuilder();
    HashMap<String, String[]> p = new HashMap<>();
    p.put(object, select);
    log.debug("appear: {}", ctx.getAppear());
    ctx.setMatchModel();
    getMatch(ctx, object, p, schemaData, matchStr);
    ctx.setOptionalModel();
    getOptional(ctx, object, optional, schemaData, matchStr);
    withObjStr.append(object);
    ctx.setMatchModel();
    getMatch(ctx, object, match, schemaData, matchStr);
    // order by xx where xx
    ctx.getAppear().forEach(e -> {
      withObjStr.append(",").append(e);
    });
    matchStr.append(ctx.getMatchSb()).append("WITH ").
        append(withObjStr).append(ctx.getWithForeignSb()).
        append("\n");
    FilterLib.appendFilterStr(matchStr, filterStr);
    matchStr.append("WITH ").append(ctx.combineMatchWith()).append(" AS one\n").
        append("WITH COLLECT(one) as list\n").
        append(getPageStr("list", page, pageSize));
    return matchStr.toString();
  }


  public static String info(GraphDisposeContext ctx, DeeprRequest deeprRequest) throws Exception {
    deeprRequest.valid();
    String object = deeprRequest.getObject();
    ctx.setTargetObject(object);
    List<Map<String, Object>> filter = deeprRequest.getFilter();
    StringBuilder filterStr = new StringBuilder();
    if (filter.size() > 0) {
      FilterParser filterParser = new FilterParser(ctx);
      filterStr = filterParser.parse(object, filter);
      ctx.getDbFilterBehavior().resetDepthObjectFilter();
    }
    String[] select = deeprRequest.getSelect();
    Map<String, String[]> optional = deeprRequest.getOptional();
    Map<String, String[]> match = deeprRequest.getMatch();
    SchemaData schemaData = SchemaManager.getSchemaData(object);
    ctx.setSchemaData(schemaData);
    StringBuilder matchStr = new StringBuilder();
    StringBuilder withObjStr = new StringBuilder();
    HashMap<String, String[]> p = new HashMap<>();
    p.put(object, select);
    log.debug("appear: {}", ctx.getAppear());
    ctx.setMatchModel();
    getMatch(ctx, object, p, schemaData, matchStr);
    ctx.setOptionalModel();
    getOptional(ctx, object, optional, schemaData, matchStr);
    withObjStr.append(object);
    ctx.setMatchModel();
    getMatch(ctx, object, match, schemaData, matchStr);
    // order by xx where xx
    ctx.getAppear().forEach(e -> {
      withObjStr.append(",").append(e);
    });
    // order by xx where xx
    matchStr.append(ctx.getMatchSb()).append("WITH ").
        append(withObjStr).append(ctx.getWithForeignSb()).
        append("\n");
    FilterLib.appendFilterStr(matchStr, filterStr);
    matchStr.append("WITH ").append(ctx.combineMatchWith()).append(" AS ").append(object).
        append("\nRETURN collect(").append(object).append(")[0..1] as list");
    return matchStr.toString();
  }


  public static void getMatch(GraphDisposeContext ctx, String targetObj, Map<String, String[]> match,
                              SchemaData schemaData, StringBuilder matchStr) throws Exception {
    for (Map.Entry<String, String[]> ent : match.entrySet()) {
      String object = ent.getKey();
      boolean is_self = object.equals(targetObj);
      String[] select = match.get(object);
      HashMap<String, PropertyData> fields;
      PropertyData propertyData = null;
      boolean isRelationProperty = false;
      if (is_self)
        fields = schemaData.getFields();
      else {
        propertyData = schemaData.getPropertyData(object);
        if (propertyData == null) {
          continue;
        }
        if (propertyData.isRelationProperty()) {
          fields = schemaData.getRelationFields(object);
          isRelationProperty = true;
        } else
          fields = SchemaManager.getSchemaData(propertyData.getSchema()).getFields();
      }
      if (fields == null)
        throw new LifeException(object + ", field is null", LifeCycle.PARSING, object);
      String kvStr = FilterLib.parseSelectKeys(ctx, fields, object, select);
      log.debug("targetObj: {}, object: {}, select:{}, isrel: {}", targetObj, object, select, isRelationProperty);
      if (!is_self) {
        String matchLabel;
        String relLabel;
        if (!isRelationProperty) {
          ctx.appendMatchWith(object, kvStr);
          if (ctx.appearContains(object))
            continue;
          SchemaData foreignSchemaData = SchemaManager.getSchemaData(propertyData.getSchema());
          matchLabel = foreignSchemaData.getName();
          String forProperty = propertyData.getForProperty();
          PropertyData relProperty = schemaData.getPropertyData(forProperty);
          Type relDir = relProperty.getPoint();
          relLabel = relProperty.getRelationLabel();
          ctx.addAppear(forProperty);
          ctx.addAppear(object);
          matchStr.append(ctx.getOptional()).append("MATCH (").append(targetObj).append(")").append(getDir(forProperty, relLabel, relDir)).append("(").append(object).append(":").append(matchLabel).append(")\n");
          continue;
        }
        String forProperty = propertyData.getForProperty();
        ctx.appendMatchWith(forProperty, object, kvStr);
        if (ctx.appearContains(object))
          continue;
        relLabel = propertyData.getRelationLabel();
        Type relDir = propertyData.getPoint();
        matchLabel = SchemaManager.getSchemaData(schemaData.getPropertyData(forProperty).getSchema()).getName();
        matchStr.append(ctx.getOptional()).append("MATCH (").append(targetObj).append(")").append(getDir(object, relLabel, relDir)).append("(").append(forProperty).append(":").append(matchLabel).append(")\n");
        ctx.addAppear(object);
        ctx.addAppear(forProperty);
      } else {
        ctx.appendMatchWith(object, kvStr);
        matchStr.append(ctx.getOptional()).append("MATCH (").append(targetObj).append(":").append(schemaData.getName()).append(")\n");
      }
    }
  }

  public static StringBuilder getForeignMatch(GraphDisposeContext ctx, String parent, String object, PropertyData propertyData) throws DeeprException {
    StringBuilder sb = new StringBuilder();
    log.debug("parent: {}, object: {}, ------{}", parent, object, propertyData);
    if (ctx.appearContains(object))
      return sb;
    if (!propertyData.isRelationProperty()) {
      String forProperty = propertyData.getForProperty();
      SchemaData foreignSchema = FilterLib.getSchemaData(ctx, parent, object);
      DeeprAssert.isTrue(foreignSchema != null, object + " for " + parent + " not exists");
      SchemaData parentSchemaData = SchemaManager.getSchemaData(parent);
      SchemaData relationSchema = parentSchemaData.getRelationSchemaData(forProperty);
      PropertyData relationProperty = parentSchemaData.getPropertyData(forProperty);
      String foreignLabel = foreignSchema.getName();
      Type point = relationProperty.getPoint();
      String relationLabel = relationSchema.getName();
      ctx.addAppear(object);
      ctx.addAppear(forProperty);
      return sb.append("MATCH (").append(parent).append(")").
          append(getDir(forProperty, relationLabel, point)).
          append("(").append(object).append(":").append(foreignLabel).append(")\n");
    }
    String forProperty = propertyData.getForProperty();
    SchemaData schemaData = SchemaManager.getSchemaData(parent);
    SchemaData foreignSchema = SchemaManager.getForeignSchemaData(schemaData, forProperty);
    ctx.addAppear(object);
    ctx.addAppear(forProperty);
    return sb.append("MATCH (").append(parent).append(")").
        append(getDir(object, schemaData.getRelationSchemaData(propertyData.getName()).getName(), propertyData.getPoint())).
        append("(").append(forProperty).append(":").append(foreignSchema.getName()).append(")\n");
  }

  public static void getOptional(GraphDisposeContext ctx, String targetObj, Map<String, String[]> match,
                                 SchemaData schemaData, StringBuilder matchStr) throws Exception {
    if (match.size() > 0) {
      ctx.setOptionalModel();
      getMatch(ctx, targetObj, match, schemaData, matchStr);
    }
  }

  public static String getDir(String obj, String schema, Type dir) {
    String str = "[" + obj + ":" + schema + "]";
    if (dir == Type.OUT)
      return "-" + str + "->";
    return "<-" + str + "-";
  }

  public static String getPageStr(String object, int page, int pageSize) {
    return "RETURN SIZE(" + object + ") AS totalCount, " +
        "list[" + (page - 1) + ".." + pageSize * page + "] as list\n";
  }

}
